using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;

namespace LiteNetLib
{
	internal sealed class NetSocket
	{
		private Socket _udpSocketv4;

		private Socket _udpSocketv6;

		private Thread _threadv4;

		private Thread _threadv6;

		private volatile bool _running;

		private readonly INetSocketListener _listener;

		private static readonly IPAddress MulticastAddressV6;

		internal static readonly bool IPv6Support;

		[CompilerGenerated]
		private int _003CLocalPort_003Ek__BackingField;

		public int LocalPort
		{
			[CompilerGenerated]
			get
			{
				return _003CLocalPort_003Ek__BackingField;
			}
			[CompilerGenerated]
			private set
			{
				_003CLocalPort_003Ek__BackingField = value;
			}
		}

		public short Ttl
		{
			get
			{
				return _udpSocketv4.Ttl;
			}
			set
			{
				_udpSocketv4.Ttl = value;
			}
		}

		static NetSocket()
		{
			MulticastAddressV6 = IPAddress.Parse("FF02:0:0:0:0:0:0:1");
			IPv6Support = Socket.OSSupportsIPv6;
		}

		public NetSocket(INetSocketListener listener)
		{
			_listener = listener;
		}

		private void ReceiveLogic(object state)
		{
			Socket socket = (Socket)state;
			EndPoint remoteEP = new IPEndPoint((socket.AddressFamily == AddressFamily.InterNetwork) ? IPAddress.Any : IPAddress.IPv6Any, 0);
			byte[] array = new byte[NetConstants.MaxPacketSize];
			while (_running)
			{
				int length;
				try
				{
					if (socket.Available == 0 && !socket.Poll(5000, SelectMode.SelectRead))
					{
						continue;
					}
					length = socket.ReceiveFrom(array, 0, array.Length, SocketFlags.None, ref remoteEP);
					goto IL_00e9;
				}
				catch (SocketException ex)
				{
					switch (ex.SocketErrorCode)
					{
					case SocketError.Interrupted:
						return;
					case SocketError.MessageSize:
					case SocketError.ConnectionReset:
					case SocketError.TimedOut:
						continue;
					}
					NetDebug.WriteError("[R]Error code: {0} - {1}", (int)ex.SocketErrorCode, ex.ToString());
					_listener.OnMessageReceived(null, 0, ex.SocketErrorCode, (IPEndPoint)remoteEP);
				}
				catch (ObjectDisposedException)
				{
					break;
				}
				continue;
				IL_00e9:
				_listener.OnMessageReceived(array, length, SocketError.Success, (IPEndPoint)remoteEP);
			}
		}

		public bool Bind(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool reuseAddress)
		{
			_udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
			if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port), reuseAddress))
			{
				return false;
			}
			LocalPort = ((IPEndPoint)_udpSocketv4.LocalEndPoint).Port;
			_running = true;
			_threadv4 = new Thread(new ParameterizedThreadStart(ReceiveLogic));
			_threadv4.Name = "SocketThreadv4(" + LocalPort + ")";
			_threadv4.IsBackground = true;
			_threadv4.Start(_udpSocketv4);
			if (!IPv6Support)
			{
				return true;
			}
			_udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
			if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort), reuseAddress))
			{
				try
				{
					_udpSocketv6.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.AddMembership, new IPv6MulticastOption(MulticastAddressV6));
				}
				catch (Exception)
				{
				}
				_threadv6 = new Thread(new ParameterizedThreadStart(ReceiveLogic));
				_threadv6.Name = "SocketThreadv6(" + LocalPort + ")";
				_threadv6.IsBackground = true;
				_threadv6.Start(_udpSocketv6);
			}
			return true;
		}

		private bool BindSocket(Socket socket, IPEndPoint ep, bool reuseAddress)
		{
			socket.ReceiveTimeout = 500;
			socket.SendTimeout = 500;
			socket.ReceiveBufferSize = 1048576;
			socket.SendBufferSize = 1048576;
			try
			{
				socket.ExclusiveAddressUse = !reuseAddress;
				socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, reuseAddress);
			}
			catch
			{
			}
			if (socket.AddressFamily == AddressFamily.InterNetwork)
			{
				socket.Ttl = 255;
				try
				{
					socket.DontFragment = true;
				}
				catch (SocketException ex)
				{
					NetDebug.WriteError("[B]DontFragment error: {0}", ex.SocketErrorCode);
				}
				try
				{
					socket.EnableBroadcast = true;
				}
				catch (SocketException ex2)
				{
					NetDebug.WriteError("[B]Broadcast error: {0}", ex2.SocketErrorCode);
				}
			}
			try
			{
				socket.Bind(ep);
			}
			catch (SocketException ex3)
			{
				switch (ex3.SocketErrorCode)
				{
				case SocketError.AddressAlreadyInUse:
					if (socket.AddressFamily == AddressFamily.InterNetworkV6)
					{
						try
						{
							socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, true);
							socket.Bind(ep);
						}
						catch (SocketException ex4)
						{
							NetDebug.WriteError("[B]Bind exception: {0}, errorCode: {1}", ex4.ToString(), ex4.SocketErrorCode);
							return false;
						}
						return true;
					}
					break;
				case SocketError.AddressFamilyNotSupported:
					return true;
				}
				NetDebug.WriteError("[B]Bind exception: {0}, errorCode: {1}", ex3.ToString(), ex3.SocketErrorCode);
				return false;
			}
			return true;
		}

		public bool SendBroadcast(byte[] data, int offset, int size, int port)
		{
			bool flag;
			try
			{
				flag = _udpSocketv4.SendTo(data, offset, size, SocketFlags.None, new IPEndPoint(IPAddress.Broadcast, port)) > 0;
				if (IPv6Support)
				{
					flag = flag || _udpSocketv6.SendTo(data, offset, size, SocketFlags.None, new IPEndPoint(MulticastAddressV6, port)) > 0;
				}
			}
			catch (Exception ex)
			{
				NetDebug.WriteError("[S][MCAST]" + ex);
				return false;
			}
			return flag;
		}

		public int SendTo(byte[] data, int offset, int size, IPEndPoint remoteEndPoint, ref SocketError errorCode)
		{
			try
			{
				Socket socket = _udpSocketv4;
				if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support)
				{
					socket = _udpSocketv6;
				}
				return socket.SendTo(data, offset, size, SocketFlags.None, remoteEndPoint);
			}
			catch (SocketException ex)
			{
				switch (ex.SocketErrorCode)
				{
				case SocketError.Interrupted:
				case SocketError.NoBufferSpaceAvailable:
					return 0;
				default:
					NetDebug.WriteError("[S]" + ex);
					break;
				case SocketError.MessageSize:
					break;
				}
				errorCode = ex.SocketErrorCode;
				return -1;
			}
			catch (Exception ex2)
			{
				NetDebug.WriteError("[S]" + ex2);
				return -1;
			}
		}

		public void Close()
		{
			_running = false;
			if (_udpSocketv4 != null)
			{
				_udpSocketv4.Close();
				_udpSocketv4 = null;
			}
			if (_udpSocketv6 != null)
			{
				_udpSocketv6.Close();
				_udpSocketv6 = null;
			}
			if (_threadv4 != null)
			{
				if (_threadv4 != Thread.CurrentThread)
				{
					_threadv4.Join();
				}
				_threadv4 = null;
			}
			if (_threadv6 != null)
			{
				if (_threadv6 != Thread.CurrentThread)
				{
					_threadv6.Join();
				}
				_threadv6 = null;
			}
		}
	}
}
